<?php
//状态码列表，返回状态码时将从此数组中取出对应值
$statusTextList = array(
	100	=> 'Continue',
	101	=> 'Switching Protocols',
	200	=> 'OK',
	201	=> 'Created',
	202	=> 'Accepted',
	203	=> 'Non-Authoritative Information',
	204	=> 'No Content',
	205	=> 'Reset Content',
	206	=> 'Partial Content',
	300	=> 'Multiple Choices',
	301	=> 'Moved Permanently',
	302	=> 'Found',
	303	=> 'See Other',
	304	=> 'Not Modified',
	305	=> 'Use Proxy',
	307	=> 'Temporary Redirect',
	400	=> 'Bad Request',
	401	=> 'Unauthorized',
	402	=> 'Payment Required',
	403	=> 'Forbidden',
	404	=> 'Not Found',
	405	=> 'Method Not Allowed',
	406	=> 'Not Acceptable',
	407	=> 'Proxy Authentication Required',
	408	=> 'Request Timeout',
	409	=> 'Conflict',
	410	=> 'Gone',
	411	=> 'Length Required',
	412	=> 'Precondition Failed',
	413	=> 'Request Entity Too Large',
	414	=> 'Request-URI Too Long',
	415	=> 'Unsupported Media Type',
	416	=> 'Requested Range Not Satisfiable',
	417	=> 'Expectation Failed',
	422	=> 'Unprocessable Entity',
	500	=> 'Internal Server Error',
	501	=> 'Not Implemented',
	502	=> 'Bad Gateway',
	503	=> 'Service Unavailable',
	504	=> 'Gateway Timeout',
	505	=> 'HTTP Version Not Supported'
);

/**
 * post数据获取
 *
 * 将获取到的post数据的对应名称数据返回
 *
 * @param string $postDataName post数据变量名
 * @return string/null返回post变量值，如果不存在这个变量返回null
 */

function getPost($postDataName) {
	if(isset($_POST[$postDataName])) {
		return $_POST[$postDataName];
	}
	else {
		return null;
	}
}

/**
 * get数据获取
 *
 * 将获取到的get数据的对应名称数据返回
 *
 * @param string $getDataName get数据变量名
 * @return string/null返回get变量值，如果不存在这个变量返回null
 */

function getGet($getDataName) {
	if(isset($_GET[$getDataName])) {
		return $_GET[$getDataName];
	}
	else {
		return null;
	}
}

/**
 * 文件信息获取
 *
 * 将上传的文件信息返回
 *
 * @param string $fileInputName 前端file input的name值 
 * @return array 返回文件信息数组，如果不存在这个数组就返回null
 */

function getFile($fileInputName) {
	if(isset($_FILES[$fileInputName])) {
		return $_FILES[$getDataName];
	}
	return null;
}

/**
 * cookie数据获取
 *
 * 将对应名称的cookie值返回
 *
 * @param string $cookieName cookie数据变量名
 * @return string/null 返回cookie变量值，如果不存在这个变量返回null
 */

function getCookie($cookieName) {
	if(isset($_COOKIE[$cookieName])) {
		return $_COOKIE[$cookieName];
	}
	else {
		return null;
	}
}

/**
 * cookie数据清除
 *
 * 清除对应名称的cookie数据
 *
 * @param string $cookieName cookie数据变量名
 * @return bool 清除成功或失败
 */

function delCookie($cookieName) {
	if(isset($_COOKIE[$cookieName])) {
		setcookie($cookieName, '', time()-3600);
		return true;
	}
	return false;
}

/**
 * php://input数据获取
 *
 * 获取到php://input的数据并返回
 *
 * @param empty
 * @return mixed 返回的数据
 */

function getRawInput() {
	$data = file_get_contents('php://input');
	return $data;
}

/**
 * 获取状态码字符串函数
 *
 * 通过状态码获得状态码对应的字符串
 *
 * @param int $code 状态码
 * @return string 返回的状态字符串
 */

function getStatus($code) {
	//声明此方法内可用此全局变量
	global $statusTextList;
	$code = (int)$code;
	return isset($statusTextList[$code]) ? $statusTextList[$code] : 'unknown';
}

/**
 * 设置头部头部状态码函数
 *
 * 可用于某些头部输出时顺带输出状态码
 *
 * @param int $code 状态码 , string $text 状态码字符串
 * @return void
 */

function setStatus($code, $text = null) {
	$code  = (int)$code;
	if(is_null($text)) {
		$text = getStatus($code);
	}
	//组合HTTP协议版本号和状态码与状态字符串，如HTTP/1.1 200 OK
	$status = PROTOCOL . ' ' . $code . ' ' . $text;
	header($status);
}

/**
 * 获取url状态码
 *
 * 访问URL地址并返回header HTTP状态码
 *
 * @param string $url 访问的url地址
 * @return int HTTP状态码
 */

function getCurlHttpCode($url) {
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_TIMEOUT, 200); 
	curl_setopt($curl, CURLOPT_HEADER, FALSE); 
	curl_setopt($curl, CURLOPT_NOBODY, FALSE); 
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); 
	curl_setopt($curl, CURLOPT_FOLLOWLOCATION, FALSE); 
	curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET'); 
	curl_exec($curl);
	$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
	return $httpCode;
}

/**
 * 获取配置数据函数
 *
 * 获取所有配置数据，并可通过$config参数覆盖原配置数据，此处采用引用，可减少数据内存占用
 *
 * @param $config array 用于替换原配置数据的新数据（默认空）
 * @return array 获得的配置数据数组 
 */

function &getConfig($config = null) {
	//静态变量$configs用来存储配置数组，之后调用本函数将可从中直接返回数组
	static $configs = null;
	
	if(!is_null($config) && !empty($config)) {
		//当存在覆盖配置数组的数组时
		$configs = $config;
		return $configs;
	}
	else if(!is_null($configs)) {
		//当设置过静态变量$configs时
		return $configs;
	}
	else {
		//以上不存在则读取配置文件获得配置
		if(!file_exists(BASEPATH . 'config.php')) {
			showError('error', '核心文件丢失', '配置文件config.php缺失', 404);
		}
		require(BASEPATH . 'config.php');
		$configs = $config;
		return $configs;
	}
}

/**
 * 获取配置值函数
 *
 * 可获取单项配置值，如basePath
 *
 * @param $itemName string 配置名
 * @return mix 返回对应配置名的配置值
 */

function getConfigItem($itemName) {
	$config = &getConfig();
	return isset($config[$itemName]) ? $config[$itemName] : null;
}

/**
 * 设置配置值函数
 *
 * 可设置或新建某项配置（这一切设置只是临时性的，当离开站点配置将恢复
 *
 * @param $itemName string 配置名 , $itemVal string 配置值
 * @return mix 设置的配置值
 */

function setConfigItem($itemName, $itemVal) {
	$config = &getConfig();
	$config[$itemName] = $itemVal;
	//原配置数组在上一条语句修改或增加后覆盖回原配置数组达到设置配置值作用
	getConfig($config);
	return $itemVal;
}

/**
 * debug状态判断函数
 *
 * 此函数能够返回debug开启状态
 *
 * @param void
 * @return bool 是否已开启debug
 */

function isDebug() {
	$debug = getConfigItem('debug');
	if($debug == true) {
		return true;
	}
	return false;
}

/**
 * 获取客户端ip函数
 *
 * 通过此函数可取到用户客户端的准确ip地址
 *
 * @param void
 * @return string clientIP 客户端ip地址
 */

function getClientIP() {
	if(getenv('HTTP_CLIENT_IP')) {
		return getenv('HTTP_CLIENT_IP');
	}
	if(getenv('HTTP_X_FORWARDED_FOR')) {
		return getenv('HTTP_X_FORWARDED_FOR');
	}
	if(getenv('REMOTE_ADDR')) {
		return getenv('REMOTE_ADDR');
	}
	return 'unknown';
}

/**
 * session存在判断函数
 *
 * 如果提供的session键名所定义的数据存在则返回true否则返回false
 *
 * @param $sessionName string session键名
 * @return bool 是否存在该session
 */

function isSession($sessionName) {
	if(isset($_SESSION[$sessionName])) {
		return true;
	}
	return false;
}

/**
 * 获取session值函数
 *
 * 当获取到的时字符串值时直接返回，当值为json字符串则转换为数组返回
 *
 * @param $sessionName string session键名 , $notDeocde bool json字符串是否转换为数组
 * @return null/string/array 获得session数组/字符串/空
 */

function getSession($sessionName, $notDecode = false) {
	if(!isset($_SESSION)) {
		session_start();
		return null;
	}
	if(isset($_SESSION[$sessionName])) {
		$jsonArray = json_decode($_SESSION[$sessionName], true);
		if(!is_null($jsonArray) && !$notDecode) {
			return $jsonArray;
		}
		return $_SESSION[$sessionName];
	}
	return null;
}

/**
 * 存入session值函数
 *
 * 当值为字符串时直接存入，当值为数组时将把数组转换为json字符串存入
 *
 * @param $sessionName string session键名 , $sessionVal string/array session键值
 * @return string/array 设置的session值
 */

function setSession($sessionName, $sessionVal) {
	if(!isset($_SESSION)) {
		session_start();
	}
	if(is_array($sessionVal)) {
		$sessionVal = json_encode($sessionVal);
	}
	delSession($sessionName);
	$_SESSION[$sessionName] = $sessionVal;
	return $sessionVal;
}

/**
 * 删除session函数
 *
 * 删除对应键名的session变量，成功返回true失败返回false
 *
 * @param $sessionName string session键名
 * @return bool 是否删除成功
 */

function delSession($sessionName) {
	if($sessionName == '-all-') {
		session_unset();
		session_destroy();
		return true;
	}
	if(isset($_SESSION[$sessionName])) {
		unset($_SESSION[$sessionName]);
		return true;
	}
	return false;
}

/**
 * 资源允许访问判断函数
 *
 * 如果资源resAllow未设置或者为false则返回false，判定为盗链
 *
 * @param void
 * @return bool 是否允许访问资源
 */

function isResAllow() {
	$safetyChain = getConfigItem('safetyChain');
	if((isSession('resAllow') && $_SESSION['resAllow'] == true) || $safetyChain == false) {
		return true;
	}
	return false;
}

/**
 * 锁定资源函数
 *
 * 锁定资源后将无法从Files控制器中获取资源（防盗链）
 *
 * @param void
 * @return void
 */

function resLock() {
	setSession('resAllow', false);
}

/**
 * 解锁资源函数
 *
 * 解锁资源后将能够在session有效期内获取资源
 *
 * @param void
 * @return void
 */

function resUnlock() {
	setSession('resAllow', true);
}

/**
 * UTF-8编码转换GBK编码函数
 *
 * 将utf-8编码的字符串转换为gbk编码的字符串
 *
 * @param $str string 要进行转换的字符串
 * @return string 转换完成的字符串
 */

function convertGBK($str) {
	return empty($str) ? '' : iconv('utf-8', 'gb2312', $str);
}

/**
 * GBK编码转换UTF-8编码函数
 *
 * 将gbk编码的字符串转换为utf-8编码的字符串
 *
 * @param $str string 要进行转换的字符串
 * @return string 转换完成的字符串
 */

function convertUTF8($str) {
	return empty($str) ? '' : iconv('gb2312', 'utf-8', $str);
}

/**
 * class加载器函数
 *
 * 将class文件引入进来并实例化返回实例
 *
 * @param $className string 类名 , $params array 给构造函数做参数的数据数组 , $classPath string 类文件路径
 * @return instance 新实例化的实例的类实例
 */

function &loadClass($className, $params = null, $classPath = CLSPATH) {
	//用于存放类实例的静态数组
	static $classes = array();
	//如果已存在该类名的类实例则直接返回实例无需重复加载文件
	if(isset($classes[$className])) {
		return $classes[$className];
	}
	//检查类文件是否存在和类是否已存在实例在代码中
	if(!file_exists($classPath . $className . '.php')) {
		showError('error', '类文件不存在', '找不到名为 ' . $className . '.php 的类文件', 404);
	}
	else if(class_exists($className, true)) {
		showError('error', '类实例冲突', $className, '这个类已经存在，存在多个将发生冲突');
	}
	require $classPath . $className . '.php';
	//如果有参数则传给构造函数
	$classes[$className] = is_null($params) ? new $className() : new $className($params);
	//返回类实例
	return $classes[$className];
}

/**
 * 控制器加载函数
 *
 * 使用此函数载入控制器
 *
 * @param $controllerName string 控制器名称 , $controllerParams array 控制器参数
 * @return controller 控制器实例
 */

function loadController($controllerName) {
	$controllerPath = CONPATH . $controllerName . '.php';
	if(!file_exists($controllerPath)) {
		_log('找不到控制器文件' . $controllerName);
		showError('error', '控制器未找到', '找不到控制器文件，系统无法完成此请求', 404);
	}
	$controller = &loadClass($controllerName, null, CONPATH);
	return $controller;
}

/**
 * 包加载函数
 *
 * 使用此函数能够帮助载入一个包
 *
 * @param string $packageName 包名（文件夹名） , array $relyonList 包依赖文件名列表 , array $params 传递给包的参数 , string $packagePath 包（文件夹）所在路径
 * @return class 包
 */

function &loadPackage($packageName, $relyonList = array(), $params = array(), $packagePath = SYSPATH) {
	static $packages = array();
	if(isset($packages[$packageName])) {
		for($r = 0;$r < count($packages[$packageName]['relyonList']);$r++) {
			loadPackageRelyon($packageName, $packages[$packageName]['relyonList'][$r]);
		}
		return $packages[$packageName]['package'];
	}
	else if(class_exists($packageName, true)) {
		showError('error', '重名的类或包', '这个包或同名类已经被载入过，禁止再次载入');
	}
	$packagePath = $packagePath . $packageName . '/' . $packageName . '.php';
	if(!file_exists($packagePath)) {
		_log('找不到' . $packageName . '这个包');
		showError('error', '包加载错误', '找不到名为' . $packageName . '的包文件');
	}
	for($r = 0;$r < count($relyonList);$r++) {
		loadPackageRelyon($packageName, $relyonList[$r]);
	}
	include_once $packagePath;
	$packages[$packageName] = array(
		'package' => null,
		'relyonList' => array()
	);
	$packages[$packageName]['package'] = empty($params) ? new $packageName() : $packageName($params);
	$packages[$packageName]['relyonList'] = $relyonList;
	return $packages[$packageName]['package'];
}

/**
 * 包依赖加载函数
 *
 * 此函数只由包加载函数调用
 *
 * @param string $packageName 包名（文件夹） , string $relyonName 依赖文件名 , string $relyonPath 依赖文件所在路径
 * @return 引入包依赖文集
 */

function loadPackageRelyon($packageName, $relyonName, $relyonPath = null) {
	if(is_null($relyonPath)) {
		$relyonPath = SYSPATH . 'packages/' . $packageName . '/';
	}
	if(!file_exists($relyonPath)) {
		_log('找不到包依赖 ' . $packageName . ':' . $relyonName);
		showError('error', '找不到包依赖', '找不到包的依赖文件，无法完成加载');
	}
	include_once $relyonPath . $relyonName . '.php';
}

/**
 * 调试消息输出
 *
 * 使用此函数能够在框架运行时将消息输出到浏览器控制台中
 *
 * @param string $msg 消息内容
 * @return void 设置msgTemp的session值
 */

function showMsg($msg) {
	if(!isDebug()) {
		return;
	}
	if(is_array($msg)) {
		$msg = var_export($msg, true);
	}
	else if(!is_string($msg) && !is_numeric($msg)) {
		showError('error', '消息输出错误', 'showMsg只能输出字符串、数字、数组');
	}
	$file = '*';
	$line = '*';
	$debug = debug_backtrace();
	$file = $debug[0]['file'];
	$line = $debug[0]['line'];
	$oldMsg = getSession('msgTemp');
	delSession('msgTemp');
	setSession('msgTemp', $oldMsg . (empty($oldMsg) ? "[{$file}:{$line}]\n" : "\n----------------\n[{$file}:{$line}]\n") . $msg);
}

/**
 * 错误输出函数
 *
 * 用于整合错误数据并使用页面展示错误内容
 *
 * @param $type string 错误类型(error/exception), $header int/string 错误的标题内容 , $msg string 错误的内容 , $code int 错误状态码 , $template string 模板文件名 , $hideFile boolean 是否隐藏文件路径敏感信息
 * @return errorPage/config:debug 输出处理过的错误页面或非debug模式下返回debug配置项值
 */

function showError($type, $header, $msg, $code = 500, $template = null, $hideFile = false, $filePath = '[hidden]', $line = '[hidden]') {
	if(is_null($template)) {
		$template = getConfigItem('errorTemplate');
	}
	//获得调用此函数语句的文件路径和行数等
	$errorObj = debug_backtrace();
	//检查是否存在关于调用的文件与代码行信息，如果不存在则不获取避免出错
	if($filePath == '[hidden]' && $line == '[hidden]' && isset($errorObj[0]) && isset($errorObj[0]['file']) && $hideFile == false) {
		$filePath = $errorObj[0]['file'];
		$line = $errorObj[0]['line'];
	}
	else if($filePath != '[hidden]') {
		_log("发生错误：[异常类型:{$type},错误码:{$code},错误行数{$line},错误标题:{$header},错误信息:{$msg},文件路径:{$filePath}]");
	}
	//如果$header是一个数字状态码则获取该状态码对应的状态字符串作为header
	if(is_numeric($header)) {
		$code = $header;
		$header = $code . ' ' . getStatus($header);
	}
	$exception = &loadClass('Exceptions');
	$errorPage = $exception->showError($type, $header, $msg, $code, $filePath, $line, $template);
	echo $errorPage;
	exit(1);
}

/**
 * 错误处理函数
 *
 * 用于替代原有错误处理的自定义错误处理器函数
 *
 * @param $serverity constant 错误等级 , $msg string 错误内容 ， $filePath string 出错文件路径 , $line int 出错所在行数
 * @return errorPage
 */

 function errorHandler($serverity, $msg, $filePath, $line) {
 	showError('error', '框架内部错误', $msg, 500, null, false, $filePath, $line);
 }

 /**
  * 异常处理函数
  *
  * 用于替代原有异常处理的自定义异常处理器函数
  *
  * @param $exceptionObj object 抛出的异常对象
  * @return exceptionPage
  */

 function exceptionHandler($exceptionObj) {
 	showError('exception', '框架内部警告', $exceptionObj->getMessage(), 500, null, false, $exceptionObj->getFile(), $exceptionObj->getLine());
 }

 /**
  * 框架异常日志写入函数
  *
  * 用于写入每次框架发生异常时的报错信息，以方便开发者调试，可在BASE/config.php中开启或关闭本功能
  *
  * @param $text string 写入的内容
  * @return true 成功输出到log文件，位于BASE/sys/logs/ , false 未开启log功能 ， exceptionPage 不支持fopen函数
  */

 function _log($text) {
 	//获取是否开启了记录系统日志功能
 	$logEvent = getConfigItem('logEvent');
 	if($logEvent == false) {
 		return false;
 	}
 	else if(!function_exists('fopen')) {
 		//如果php未开启fopen则报错
 		showError('exception', 'php部分函数未启用', '服务器未开启fopen函数，VCMINI无法完成日志采集，请在php.ini中开启它');
 		return false;
 	}
 	//获得调用此函数语句的文件路径和行数等
	$errorObj = debug_backtrace();
	$filePath = 'unknown';
	$line = 'unknown';
	//检查是否存在关于调用的文件与代码行信息，如果不存在则不获取避免出错
	if(isset($errorObj[0]) && isset($errorObj[0]['file'])) {
		$filePath = $errorObj[0]['file'];
		$line = $errorObj[0]['line'];
	}
	if(is_array($text)) {
		$text = var_export($text, true);
	}
 	$from = str_replace('.php', '', basename($filePath));
 	//换行
 	$text = '[' . date('H:i:s') . '][' . CLIP  . ']' . $from . '-' . $line . ': ' . $text  . "\r\n";
 	//以日期为日志文件名，同一天的日志只写入同一个文件
 	$filePath = SYSPATH . 'logs/' . date('Y-m-d') . '.log';
 	$file = fopen($filePath, 'a+');
 	//写入log内容
 	fwrite($file, $text);
 	fclose($file);
 	return true;
 }